package com.tuniu.agents.common.command;

import com.tuniu.agents.command.*;
import com.tuniu.agents.common.AgentsConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Command Operation Manager
 * Responsible for managing commands and their states, supporting storage and retrieval of command states by session ID and key.
 */
@Component
@Slf4j
public class CommandManager implements CommandStorage, CommandFactory {

    /**
     * Spring application context
     */
    private ApplicationContext applicationContext;

    /**
     * Matcher mapping table
     */
    private Map<String, CommandMatch> matchMap;

    /**
     * Action mapping table
     */
    private Map<String, CommandAction> actionMap;

    /**
     * Command state storage interface for abstracting storage implementation
     */
    private CommandStorage commandStorage;

    @Autowired
    public CommandManager(ApplicationContext applicationContext, CommandStorage commandStorage) {
        this.applicationContext = applicationContext;
        this.matchMap = applicationContext.getBeansOfType(CommandMatch.class);
        this.actionMap = applicationContext.getBeansOfType(CommandAction.class);
        this.commandStorage = commandStorage;
    }

    @Override
    public void saveCommandConfig(String conversationId, CommandConfig config) {
        this.commandStorage.saveCommandConfig(conversationId, config);
    }

    @Override
    public void saveCommandConfigs(String conversationId, List<CommandConfig> configs) {
        this.commandStorage.saveCommandConfigs(conversationId, configs);
    }

    @Override
    public CommandConfig getCommandConfig(String conversationId, String id) {
        return this.commandStorage.getCommandConfig(conversationId, id);
    }

    @Override
    public Map<String, CommandConfig> getAllCommandConfigs(String conversationId) {
        return this.commandStorage.getAllCommandConfigs(conversationId);
    }

    @Override
    public void deleteCommandConfig(String conversationId, String id) {
        this.commandStorage.deleteCommandConfig(conversationId, id);
    }

    @Override
    public void updateCommandConfig(String conversationId, CommandConfig config) {
        this.commandStorage.updateCommandConfig(conversationId, config);
    }

    /**
     * Get all command states
     *
     * @param conversationId conversation id
     * @return Mapping of commands and their states
     */
    public Map<String, Command> getAllCommandStates(String conversationId) {
        Map<String, CommandConfig> commandConfigs = this.commandStorage.getAllCommandConfigs(conversationId);
        Map<String, Command> result = new HashMap<>();
        for (Map.Entry<String, CommandConfig> entry : commandConfigs.entrySet()) {
            CommandConfig commandConfig = entry.getValue();
            Command command = createCommand(commandConfig);
            if (command != null) {
                result.put(entry.getKey(), command);
            }
        }
        return result;
    }

    /**
     * Get commands sorted by priority
     *
     * @param conversationId conversation id
     * @return List of commands sorted by priority
     */
    public List<CommandConfig> getCommandsByPriority(String conversationId) {
        Map<String, CommandConfig> commandConfigs = this.commandStorage.getAllCommandConfigs(conversationId);
        return commandConfigs.values().stream()
                .sorted(Comparator.comparing(CommandConfig::getPriority, Comparator.nullsLast(Comparator.naturalOrder())))
                .collect(Collectors.toList());
    }

    @Override
    public Command createCommand(CommandConfig config) {
        // Get the matcher and action.
        CommandMatch match = getMatch(config.getMatchName());
        CommandAction action = getAction(config.getActionName());

        // Create a command object.
        return new ConfigCommand(config, match, action);
    }

    @Override
    public CommandMatch getMatch(String matchName) {
        return matchMap.get(matchName);
    }

    @Override
    public CommandAction getAction(String actionName) {
        return actionMap.get(actionName);
    }

    public Command findMatchingCommand(List<Command> commands, String input, Map<String, Object> context) {
        String conversationId = String.valueOf(context.get(AgentsConstants.AgentsConversationEnvironmentKey.CONVERSATION_ID));
        Command result = null;
        for (Command command : commands) {
            if (result == null) {
                CommandMatch commandMatch = command.getMatch();
                boolean isMatch = commandMatch.matches(input, command.getParams(), context);
                if (isMatch) {
                    result = command;
                    if (CommandLifecycle.ONCE.equals(command.getLifecycle())) { // Invalidate after one call
                        commandStorage.deleteCommandConfig(conversationId, command.getId());
                    }
                }
            }
            if (CommandLifecycle.ITERATED.equals(command.getLifecycle())) {
                commandStorage.deleteCommandConfig(conversationId, command.getId());
            }
            if (CommandLifecycle.SWITCHED.equals(command.getLifecycle())) {
                if (command != result) {
                    // No match this time
                    commandStorage.deleteCommandConfig(conversationId, command.getId());
                }
            }
        }
        return result;
    }
}
